🎖️GitЯра🎖️
Node / meshtastic / Meshtastic-Android / files / feature / node / src / commonMain / kotlin / org / meshtastic / feature / node / metrics / MetricsViewModel.kt
Displaying Raw • Download
feature/node/src/commonMain/kotlin/org/meshtastic/feature/node/metrics/MetricsViewModel.kt 492afbde5c98f3efdcd367ab2f13ef3665fe7388 (492afbde) Text, 27.57 KB
T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.feature.node.metrics
Tff7b72import T7ee787androidx.compose.foundation.text.selection.SelectionContainer
Tff7b72import T7ee787androidx.compose.material3.Text
Tff7b72import T7ee787androidx.compose.ui.text.AnnotatedString
Tff7b72import T7ee787androidx.lifecycle.ViewModel
Tff7b72import T7ee787androidx.lifecycle.viewModelScope
Tff7b72import T7ee787co.touchlab.kermit.Logger
Tff7b72import T7ee787kotlinx.coroutines.flow.MutableStateFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.StateFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.combine
Tff7b72import T7ee787kotlinx.coroutines.flow.filterNotNull
Tff7b72import T7ee787kotlinx.coroutines.flow.first
Tff7b72import T7ee787kotlinx.coroutines.flow.flatMapLatest
Tff7b72import T7ee787kotlinx.coroutines.flow.flowOf
Tff7b72import T7ee787kotlinx.coroutines.flow.map
Tff7b72import T7ee787kotlinx.coroutines.flow.update
Tff7b72import T7ee787kotlinx.coroutines.launch
Tff7b72import T7ee787kotlinx.datetime.TimeZone
Tff7b72import T7ee787kotlinx.datetime.toLocalDateTime
Tff7b72import T7ee787okio.ByteString.Companion.decodeBase64
Tff7b72import T7ee787org.jetbrains.compose.resources.StringResource
Tff7b72import T7ee787org.koin.core.annotation.InjectedParam
Tff7b72import T7ee787org.koin.core.annotation.KoinViewModel
Tff7b72import T7ee787org.meshtastic.core.common.util.CommonUri
Tff7b72import T7ee787org.meshtastic.core.common.util.formatString
Tff7b72import T7ee787org.meshtastic.core.common.util.nowSeconds
Tff7b72import T7ee787org.meshtastic.core.di.CoroutineDispatchers
Tff7b72import T7ee787org.meshtastic.core.model.MeshLog
Tff7b72import T7ee787org.meshtastic.core.model.Node
Tff7b72import T7ee787org.meshtastic.core.model.TelemetryType
Tff7b72import T7ee787org.meshtastic.core.model.TracerouteOverlay
Tff7b72import T7ee787org.meshtastic.core.model.evaluateTracerouteMapAvailability
Tff7b72import T7ee787org.meshtastic.core.model.util.GeoConstants
Tff7b72import T7ee787org.meshtastic.core.model.util.TELEMETRY_CHANNEL_COUNT
Tff7b72import T7ee787org.meshtastic.core.model.util.UnitConversions
Tff7b72import T7ee787org.meshtastic.core.model.util.adcVoltage
Tff7b72import T7ee787org.meshtastic.core.model.util.oneWireTemperature
Tff7b72import T7ee787org.meshtastic.core.model.util.rxTimeOrNull
Tff7b72import T7ee787org.meshtastic.core.model.util.snrOrNull
Tff7b72import T7ee787org.meshtastic.core.model.util.withOneWireTemperature
Tff7b72import T7ee787org.meshtastic.core.repository.FileService
Tff7b72import T7ee787org.meshtastic.core.repository.MeshLogRepository
Tff7b72import T7ee787org.meshtastic.core.repository.NodeRepository
Tff7b72import T7ee787org.meshtastic.core.repository.TracerouteResponseProvider
Tff7b72import T7ee787org.meshtastic.core.repository.TracerouteSnapshotRepository
Tff7b72import T7ee787org.meshtastic.core.resources.Res
Tff7b72import T7ee787org.meshtastic.core.resources.okay
Tff7b72import T7ee787org.meshtastic.core.resources.traceroute
Tff7b72import T7ee787org.meshtastic.core.resources.view_on_map
Tff7b72import T7ee787org.meshtastic.core.ui.util.AlertManager
Tff7b72import T7ee787org.meshtastic.core.ui.util.toMessageRes
Tff7b72import T7ee787org.meshtastic.core.ui.viewmodel.safeLaunch
Tff7b72import T7ee787org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
Tff7b72import T7ee787org.meshtastic.feature.node.detail.NodeRequestActions
Tff7b72import T7ee787org.meshtastic.feature.node.domain.usecase.GetNodeDetailsUseCase
Tff7b72import T7ee787org.meshtastic.feature.node.model.MetricsState
Tff7b72import T7ee787org.meshtastic.feature.node.model.TimeFrame
Tff7b72import T7ee787org.meshtastic.proto.MeshPacket
Tff7b72import T7ee787org.meshtastic.proto.PortNum
Tff7b72import T7ee787org.meshtastic.proto.Telemetry
Tff7b72import T7ee787kotlin.time.Instant
Tff7b72import T7ee787org.meshtastic.proto.Paxcount Tff7b72as Te6edf3ProtoPaxcount
T8b949e/**
* ViewModel responsible for managing and graphing metrics (telemetry, signal strength, paxcount) for a specific node.
*/
Tf0883e@KoinViewModel
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffLongParameterListTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffTooManyFunctionsTa5d6ff"Tb4b4b4)
Tff7b72open Tff7b72class T56d364MetricsViewModelTb4b4b4(
Tf0883e@InjectedParam Tff7b72val Te6edf3destNumTb4b4b4: Tffa657IntTb4b4b4,
Tff7b72protected Tff7b72val Te6edf3dispatchersTb4b4b4: Te6edf3CoroutineDispatchersTb4b4b4,
Tff7b72private Tff7b72val Te6edf3meshLogRepositoryTb4b4b4: Te6edf3MeshLogRepositoryTb4b4b4,
Tff7b72private Tff7b72val Te6edf3tracerouteResponseProviderTb4b4b4: Te6edf3TracerouteResponseProviderTb4b4b4,
Tff7b72private Tff7b72val Te6edf3nodeRepositoryTb4b4b4: Te6edf3NodeRepositoryTb4b4b4,
Tff7b72private Tff7b72val Te6edf3tracerouteSnapshotRepositoryTb4b4b4: Te6edf3TracerouteSnapshotRepositoryTb4b4b4,
Tff7b72private Tff7b72val Te6edf3nodeRequestActionsTb4b4b4: Te6edf3NodeRequestActionsTb4b4b4,
Tff7b72private Tff7b72val Te6edf3alertManagerTb4b4b4: Te6edf3AlertManagerTb4b4b4,
Tff7b72private Tff7b72val Te6edf3getNodeDetailsUseCaseTb4b4b4: Te6edf3GetNodeDetailsUseCaseTb4b4b4,
Tff7b72private Tff7b72val Te6edf3fileServiceTb4b4b4: Te6edf3FileServiceTb4b4b4,
Tb4b4b4) Tb4b4b4: Te6edf3ViewModelTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72private Tff7b72val Te6edf3nodeIdFromRouteTb4b4b4: Tffa657Int?
Tff7b72getTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3destNum
Tff7b72private Tff7b72val Te6edf3manualNodeId Tff7b72= Te6edf3MutableStateFlowTff7b72<Tffa657Int?Tff7b72>Tb4b4b4(Tff7b72nullTb4b4b4)
Tff7b72private Tff7b72val Te6edf3activeNodeId Tff7b72=
Te6edf3combineTb4b4b4(Te6edf3MutableStateFlowTb4b4b4(Te6edf3nodeIdFromRouteTb4b4b4)Tb4b4b4, Te6edf3manualNodeIdTb4b4b4) Tb4b4b4{ Te6edf3fromRouteTb4b4b4, Te6edf3manual Tff7b72-Tff7b72> Te6edf3manual Tff7b72?: Te6edf3fromRoute Tb4b4b4}
Tff7b72private Tff7b72val Te6edf3tracerouteOverlayCache Tff7b72= Te6edf3MutableStateFlowTff7b72<Te6edf3MapTff7b72<Tffa657IntTb4b4b4, Te6edf3TracerouteOverlayTff7b72>Tff7b72>Tb4b4b4(Te6edf3emptyMapTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3stateTb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3MetricsStateTff7b72> Tff7b72=
Te6edf3activeNodeId
Tb4b4b4.Te6edf3flatMapLatest Tb4b4b4{ Te6edf3nodeId Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Te6edf3nodeId Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tff7b72returnTf0883e@flatMapLatest Te6edf3flowOfTb4b4b4(Te6edf3MetricsStateTb4b4b4.Te6edf3EmptyTb4b4b4)
Te6edf3getNodeDetailsUseCaseTb4b4b4(Te6edf3nodeIdTb4b4b4)Tb4b4b4.Te6edf3map Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3metricsState Tb4b4b4}
Tb4b4b4}
Tb4b4b4.Te6edf3stateInWhileSubscribedTb4b4b4(Te6edf3initialValue Tff7b72= Te6edf3MetricsStateTb4b4b4.Te6edf3EmptyTb4b4b4)
Tff7b72private Tff7b72val Te6edf3environmentStateTb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3EnvironmentMetricsStateTff7b72> Tff7b72=
Te6edf3activeNodeId
Tb4b4b4.Te6edf3flatMapLatest Tb4b4b4{ Te6edf3nodeId Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Te6edf3nodeId Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tff7b72returnTf0883e@flatMapLatest Te6edf3flowOfTb4b4b4(Te6edf3EnvironmentMetricsStateTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3getNodeDetailsUseCaseTb4b4b4(Te6edf3nodeIdTb4b4b4)Tb4b4b4.Te6edf3map Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3environmentState Tb4b4b4}
Tb4b4b4}
Tb4b4b4.Te6edf3stateInWhileSubscribedTb4b4b4(Te6edf3initialValue Tff7b72= Te6edf3EnvironmentMetricsStateTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3_timeFrame Tff7b72= Te6edf3MutableStateFlowTb4b4b4(Te6edf3TimeFrameTb4b4b4.Te6edf3TWENTY_FOUR_HOURSTb4b4b4)
T8b949e/** The active time window for filtering graphed data. */
Tff7b72val Te6edf3timeFrameTb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3TimeFrameTff7b72> Tff7b72= Te6edf3_timeFrame
T8b949e/** Returns the list of time frames that are actually available based on the oldest data point. */
Tff7b72val Te6edf3availableTimeFramesTb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3ListTff7b72<Te6edf3TimeFrameTff7b72>Tff7b72> Tff7b72=
Te6edf3combineTb4b4b4(Te6edf3stateTb4b4b4, Te6edf3environmentStateTb4b4b4) Tb4b4b4{ Te6edf3currentStateTb4b4b4, Te6edf3envState Tff7b72-Tff7b72>
Tff7b72val Te6edf3stateOldest Tff7b72= Te6edf3currentStateTb4b4b4.Te6edf3oldestTimestampSecondsTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3envOldest Tff7b72=
Te6edf3envStateTb4b4b4.Te6edf3environmentMetricsTb4b4b4.Te6edf3minOfOrNull Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3timeTb4b4b4.Te6edf3toLongTb4b4b4(Tb4b4b4) Tb4b4b4}Tff7b72?.Te6edf3takeIf Tb4b4b4{ Tffa657it Tff7b72> T79c0ff0 Tb4b4b4} Tff7b72?: Te6edf3nowSeconds
Tff7b72val Te6edf3oldest Tff7b72= Te6edf3listOfNotNullTb4b4b4(Te6edf3stateOldestTb4b4b4, Te6edf3envOldestTb4b4b4)Tb4b4b4.Te6edf3minOrNullTb4b4b4(Tb4b4b4) Tff7b72?: Te6edf3nowSeconds
Te6edf3TimeFrameTb4b4b4.Te6edf3entriesTb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3isAvailableTb4b4b4(Te6edf3oldestTb4b4b4) Tb4b4b4}
Tb4b4b4}
Tb4b4b4.Te6edf3stateInWhileSubscribedTb4b4b4(Te6edf3TimeFrameTb4b4b4.Te6edf3entriesTb4b4b4)
Tff7b72fun Td2a8ffsetTimeFrameTb4b4b4(Te6edf3timeFrameTb4b4b4: Te6edf3TimeFrameTb4b4b4) Tb4b4b4{
Te6edf3_timeFrameTb4b4b4.Te6edf3value Tff7b72= Te6edf3timeFrame
Tb4b4b4}
T8b949e/** Exposes filtered and unit-converted environment metrics for the UI. */
Tff7b72val Te6edf3filteredEnvironmentMetricsTb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3ListTff7b72<Te6edf3TelemetryTff7b72>Tff7b72> Tff7b72=
Te6edf3combineTb4b4b4(Te6edf3environmentStateTb4b4b4, Te6edf3_timeFrameTb4b4b4, Te6edf3stateTb4b4b4) Tb4b4b4{ Te6edf3envStateTb4b4b4, Te6edf3timeFrameTb4b4b4, Te6edf3currentState Tff7b72-Tff7b72>
Tff7b72val Te6edf3threshold Tff7b72= Te6edf3timeFrameTb4b4b4.Te6edf3timeThresholdTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3data Tff7b72= Te6edf3envStateTb4b4b4.Te6edf3environmentMetricsTb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3timeTb4b4b4.Te6edf3toLongTb4b4b4(Tb4b4b4) Tff7b72>Tff7b72= Te6edf3threshold Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3currentStateTb4b4b4.Te6edf3isFahrenheitTb4b4b4) Tb4b4b4{
Te6edf3dataTb4b4b4.Te6edf3map Tb4b4b4{ Te6edf3telemetry Tff7b72-Tff7b72>
Tff7b72val Te6edf3em Tff7b72= Te6edf3telemetryTb4b4b4.Te6edf3environment_metrics Tff7b72?: Tff7b72returnTf0883e@map Te6edf3telemetry
T8b949e// Each 1-Wire channel converts independently; an absent channel must stay null rather than
T8b949e// becoming a converted zero.
Tff7b72var Te6edf3converted Tff7b72=
Te6edf3emTb4b4b4.Te6edf3copyTb4b4b4(
Te6edf3temperature Tff7b72= Te6edf3emTb4b4b4.Te6edf3temperatureTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3UnitConversionsTb4b4b4.Te6edf3celsiusToFahrenheitTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4}Tb4b4b4,
Te6edf3soil_temperature Tff7b72= Te6edf3emTb4b4b4.Te6edf3soil_temperatureTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3UnitConversionsTb4b4b4.Te6edf3celsiusToFahrenheitTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tff7b72for Tb4b4b4(Te6edf3channel Tff7b72in T79c0ff0 Te6edf3until Te6edf3TELEMETRY_CHANNEL_COUNTTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3celsius Tff7b72= Te6edf3emTb4b4b4.Te6edf3oneWireTemperatureTb4b4b4(Te6edf3channelTb4b4b4) Tff7b72?: Tff7b72continue
Te6edf3converted Tff7b72=
Te6edf3convertedTb4b4b4.Te6edf3withOneWireTemperatureTb4b4b4(Te6edf3channelTb4b4b4, Te6edf3UnitConversionsTb4b4b4.Te6edf3celsiusToFahrenheitTb4b4b4(Te6edf3celsiusTb4b4b4)Tb4b4b4)
Tb4b4b4}
Te6edf3telemetryTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3environment_metrics Tff7b72= Te6edf3convertedTb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3data
Tb4b4b4}
Tb4b4b4}
Tb4b4b4.Te6edf3stateInWhileSubscribedTb4b4b4(Te6edf3emptyListTb4b4b4(Tb4b4b4)Tb4b4b4)
T8b949e/** Exposes graphing data specifically for the filtered environment metrics. */
Tff7b72val Te6edf3environmentGraphingDataTb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3EnvironmentGraphingDataTff7b72> Tff7b72=
Te6edf3filteredEnvironmentMetrics
Tb4b4b4.Te6edf3map Tb4b4b4{ Te6edf3filtered Tff7b72-Tff7b72> Te6edf3EnvironmentMetricsStateTb4b4b4(Te6edf3filteredTb4b4b4)Tb4b4b4.Te6edf3environmentMetricsForGraphingTb4b4b4(Te6edf3useFahrenheit Tff7b72= Tff7b72falseTb4b4b4) Tb4b4b4}
Tb4b4b4.Te6edf3stateInWhileSubscribedTb4b4b4(Te6edf3EnvironmentGraphingDataTb4b4b4(Te6edf3emptyListTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3emptyListTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4)
T8b949e/** Exposes filtered and decoded pax metrics for the UI. */
Tff7b72val Te6edf3filteredPaxMetricsTb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3ListTff7b72<Te6edf3PairTff7b72<Te6edf3MeshLogTb4b4b4, Te6edf3ProtoPaxcountTff7b72>Tff7b72>Tff7b72> Tff7b72=
Te6edf3combineTb4b4b4(Te6edf3stateTb4b4b4, Te6edf3_timeFrameTb4b4b4) Tb4b4b4{ Te6edf3currentStateTb4b4b4, Te6edf3timeFrame Tff7b72-Tff7b72>
Tff7b72val Te6edf3threshold Tff7b72= Te6edf3timeFrameTb4b4b4.Te6edf3timeThresholdTb4b4b4(Tb4b4b4)
Te6edf3currentStateTb4b4b4.Te6edf3paxMetrics
Tb4b4b4.Te6edf3filter Tb4b4b4{ Tb4b4b4(Tffa657itTb4b4b4.Te6edf3received_date Tff7b72/ T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4) Tff7b72>Tff7b72= Te6edf3threshold Tb4b4b4}
Tb4b4b4.Te6edf3mapNotNull Tb4b4b4{ Te6edf3log Tff7b72-Tff7b72> Te6edf3decodePaxFromLogTb4b4b4(Te6edf3logTb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{ Te6edf3log Te6edf3to Tffa657it Tb4b4b4} Tb4b4b4}
Tb4b4b4}
Tb4b4b4.Te6edf3stateInWhileSubscribedTb4b4b4(Te6edf3emptyListTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3lastTraceRouteTimeTb4b4b4: Te6edf3StateFlowTff7b72<Tffa657Long?Tff7b72> Tff7b72= Te6edf3nodeRequestActionsTb4b4b4.Te6edf3lastTracerouteTime
Tff7b72val Te6edf3lastRequestNeighborsTimeTb4b4b4: Te6edf3StateFlowTff7b72<Tffa657Long?Tff7b72> Tff7b72=
Te6edf3combineTb4b4b4(Te6edf3nodeRequestActionsTb4b4b4.Te6edf3lastRequestNeighborTimesTb4b4b4, Te6edf3activeNodeIdTb4b4b4) Tb4b4b4{ Te6edf3mapTb4b4b4, Te6edf3id Tff7b72-Tff7b72> Te6edf3idTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3mapTff7b72[Tffa657itTff7b72] Tb4b4b4} Tb4b4b4}
Tb4b4b4.Te6edf3stateInWhileSubscribedTb4b4b4(Tff7b72nullTb4b4b4)
Tff7b72fun Td2a8ffgetUserTb4b4b4(Te6edf3nodeNumTb4b4b4: Tffa657IntTb4b4b4) Tff7b72= Te6edf3nodeRepositoryTb4b4b4.Te6edf3getUserTb4b4b4(Te6edf3nodeNumTb4b4b4)
T8b949e/** Persists a user-editable label (e.g. "Solar", "Battery") for a power-metrics channel (0-based index). */
Tff7b72fun Td2a8ffsetPowerChannelLabelTb4b4b4(Te6edf3nodeNumTb4b4b4: Tffa657IntTb4b4b4, Te6edf3channelIndexTb4b4b4: Tffa657IntTb4b4b4, Te6edf3labelTb4b4b4: Tffa657StringTb4b4b4) Tff7b72=
Te6edf3safeLaunchTb4b4b4(Te6edf3context Tff7b72= Te6edf3dispatchersTb4b4b4.Te6edf3ioTb4b4b4, Te6edf3tag Tff7b72= Ta5d6ff"Ta5d6ffsetPowerChannelLabelTa5d6ff"Tb4b4b4) Tb4b4b4{
T8b949e// Atomic in the DAO: reads current labels from the DB (not stale ViewModel state) so quick successive
T8b949e// saves to different channels can't clobber each other.
Te6edf3nodeRepositoryTb4b4b4.Te6edf3updatePowerChannelLabelTb4b4b4(Te6edf3nodeNumTb4b4b4, Te6edf3channelIndexTb4b4b4, Te6edf3labelTb4b4b4)
Tb4b4b4}
Tff7b72fun Td2a8ffdeleteLogTb4b4b4(Te6edf3uuidTb4b4b4: Tffa657StringTb4b4b4) Tff7b72=
Te6edf3safeLaunchTb4b4b4(Te6edf3context Tff7b72= Te6edf3dispatchersTb4b4b4.Te6edf3ioTb4b4b4, Te6edf3tag Tff7b72= Ta5d6ff"Ta5d6ffdeleteLogTa5d6ff"Tb4b4b4) Tb4b4b4{ Te6edf3meshLogRepositoryTb4b4b4.Te6edf3deleteLogTb4b4b4(Te6edf3uuidTb4b4b4) Tb4b4b4}
Tff7b72fun Td2a8ffgetTracerouteOverlayTb4b4b4(Te6edf3requestIdTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Te6edf3TracerouteOverlay? Tb4b4b4{
Tff7b72val Te6edf3cached Tff7b72= Te6edf3tracerouteOverlayCacheTb4b4b4.Te6edf3valueTff7b72[Te6edf3requestIdTff7b72]
Tff7b72if Tb4b4b4(Te6edf3cached Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tff7b72return Te6edf3cached
Tff7b72val Te6edf3overlay Tff7b72=
Te6edf3tracerouteResponseProviderTb4b4b4.Te6edf3tracerouteResponseTb4b4b4.Te6edf3value
Tff7b72?.Te6edf3takeIf Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3requestId Tff7b72=Tff7b72= Te6edf3requestId Tb4b4b4}
Tff7b72?.Te6edf3let Tb4b4b4{ Te6edf3response Tff7b72-Tff7b72>
Te6edf3TracerouteOverlayTb4b4b4(
Te6edf3requestId Tff7b72= Te6edf3responseTb4b4b4.Te6edf3requestIdTb4b4b4,
Te6edf3forwardRoute Tff7b72= Te6edf3responseTb4b4b4.Te6edf3forwardRouteTb4b4b4,
Te6edf3returnRoute Tff7b72= Te6edf3responseTb4b4b4.Te6edf3returnRouteTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tff7b72?.Te6edf3takeIf Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3hasRoutes Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3overlay Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3tracerouteOverlayCacheTb4b4b4.Te6edf3update Tb4b4b4{ Tffa657it Tff7b72+ Tb4b4b4(Te6edf3requestId Te6edf3to Te6edf3overlayTb4b4b4) Tb4b4b4}
Tb4b4b4}
Tff7b72return Te6edf3overlay
Tb4b4b4}
Tff7b72fun Td2a8fftracerouteSnapshotPositionsTb4b4b4(Te6edf3logUuidTb4b4b4: Tffa657StringTb4b4b4) Tff7b72= Te6edf3tracerouteSnapshotRepositoryTb4b4b4.Te6edf3getSnapshotPositionsTb4b4b4(Te6edf3logUuidTb4b4b4)
Tff7b72fun Td2a8ffclearTracerouteResponseTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3tracerouteResponseProviderTb4b4b4.Te6edf3clearTracerouteResponseTb4b4b4(Tb4b4b4)
Tff7b72fun Td2a8ffpositionedNodeNumsTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3SetTff7b72<Tffa657IntTff7b72> Tff7b72=
Te6edf3nodeRepositoryTb4b4b4.Te6edf3nodeDBbyNumTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3valuesTb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3validPosition Tff7b72!Tff7b72= Tff7b72null Tb4b4b4}Tb4b4b4.Te6edf3numSetTb4b4b4(Tb4b4b4)
Tff7b72private Tff7b72fun Td2a8ffListTff7b72<Te6edf3NodeTff7b72>Tb4b4b4.Te6edf3numSetTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3SetTff7b72<Tffa657IntTff7b72> Tff7b72= Te6edf3map Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3num Tb4b4b4}Tb4b4b4.Te6edf3toSetTb4b4b4(Tb4b4b4)
Tff7b72init Tb4b4b4{
Te6edf3safeLaunchTb4b4b4(Te6edf3tag Tff7b72= Ta5d6ff"Ta5d6fftracerouteCollectorTa5d6ff"Tb4b4b4) Tb4b4b4{
Te6edf3tracerouteResponseProviderTb4b4b4.Te6edf3tracerouteResponseTb4b4b4.Te6edf3filterNotNullTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3collect Tb4b4b4{ Te6edf3response Tff7b72-Tff7b72>
Tff7b72val Te6edf3overlay Tff7b72=
Te6edf3TracerouteOverlayTb4b4b4(
Te6edf3requestId Tff7b72= Te6edf3responseTb4b4b4.Te6edf3requestIdTb4b4b4,
Te6edf3forwardRoute Tff7b72= Te6edf3responseTb4b4b4.Te6edf3forwardRouteTb4b4b4,
Te6edf3returnRoute Tff7b72= Te6edf3responseTb4b4b4.Te6edf3returnRouteTb4b4b4,
Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3overlayTb4b4b4.Te6edf3hasRoutesTb4b4b4) Tb4b4b4{
Te6edf3tracerouteOverlayCacheTb4b4b4.Te6edf3update Tb4b4b4{ Tffa657it Tff7b72+ Tb4b4b4(Te6edf3responseTb4b4b4.Te6edf3requestId Te6edf3to Te6edf3overlayTb4b4b4) Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffMetricsViewModel createdTa5d6ff" Tb4b4b4}
Tb4b4b4}
Tff7b72fun Td2a8ffclearPositionTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3safeLaunchTb4b4b4(Te6edf3context Tff7b72= Te6edf3dispatchersTb4b4b4.Te6edf3ioTb4b4b4, Te6edf3tag Tff7b72= Ta5d6ff"Ta5d6ffclearPositionTa5d6ff"Tb4b4b4) Tb4b4b4{
Tb4b4b4(Te6edf3manualNodeIdTb4b4b4.Te6edf3value Tff7b72?: Te6edf3nodeIdFromRouteTb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{
Te6edf3meshLogRepositoryTb4b4b4.Te6edf3deleteLogsTb4b4b4(Tffa657itTb4b4b4, Te6edf3PortNumTb4b4b4.Te6edf3POSITION_APPTb4b4b4.Te6edf3valueTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tff7b72fun Td2a8ffclearLocalStatsTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3safeLaunchTb4b4b4(Te6edf3context Tff7b72= Te6edf3dispatchersTb4b4b4.Te6edf3ioTb4b4b4, Te6edf3tag Tff7b72= Ta5d6ff"Ta5d6ffclearLocalStatsTa5d6ff"Tb4b4b4) Tb4b4b4{
Tb4b4b4(Te6edf3manualNodeIdTb4b4b4.Te6edf3value Tff7b72?: Te6edf3nodeIdFromRouteTb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{ Te6edf3meshLogRepositoryTb4b4b4.Te6edf3deleteLocalStatsLogsTb4b4b4(Tffa657itTb4b4b4) Tb4b4b4}
Tb4b4b4}
Tff7b72fun Td2a8ffrequestPositionTb4b4b4(Tb4b4b4) Tb4b4b4{
Tb4b4b4(Te6edf3manualNodeIdTb4b4b4.Te6edf3value Tff7b72?: Te6edf3nodeIdFromRouteTb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{
Te6edf3viewModelScopeTb4b4b4.Te6edf3launch Tb4b4b4{ Te6edf3nodeRequestActionsTb4b4b4.Te6edf3requestPositionTb4b4b4(Tffa657itTb4b4b4, Te6edf3stateTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3nodeTff7b72?.Te6edf3userTff7b72?.Te6edf3long_name Tff7b72?: Ta5d6ff"Ta5d6ff"Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72fun Td2a8ffrequestTelemetryTb4b4b4(Te6edf3typeTb4b4b4: Te6edf3TelemetryTypeTb4b4b4) Tb4b4b4{
Tb4b4b4(Te6edf3manualNodeIdTb4b4b4.Te6edf3value Tff7b72?: Te6edf3nodeIdFromRouteTb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{
Te6edf3viewModelScopeTb4b4b4.Te6edf3launch Tb4b4b4{
Te6edf3nodeRequestActionsTb4b4b4.Te6edf3requestTelemetryTb4b4b4(Tffa657itTb4b4b4, Te6edf3stateTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3nodeTff7b72?.Te6edf3userTff7b72?.Te6edf3long_name Tff7b72?: Ta5d6ff"Ta5d6ff"Tb4b4b4, Te6edf3typeTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72fun Td2a8ffrequestTracerouteTb4b4b4(Tb4b4b4) Tb4b4b4{
Tb4b4b4(Te6edf3manualNodeIdTb4b4b4.Te6edf3value Tff7b72?: Te6edf3nodeIdFromRouteTb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{
Te6edf3viewModelScopeTb4b4b4.Te6edf3launch Tb4b4b4{ Te6edf3nodeRequestActionsTb4b4b4.Te6edf3requestTracerouteTb4b4b4(Tffa657itTb4b4b4, Te6edf3stateTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3nodeTff7b72?.Te6edf3userTff7b72?.Te6edf3long_name Tff7b72?: Ta5d6ff"Ta5d6ff"Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72fun Td2a8ffrequestNeighborInfoTb4b4b4(Tb4b4b4) Tb4b4b4{
Tb4b4b4(Te6edf3manualNodeIdTb4b4b4.Te6edf3value Tff7b72?: Te6edf3nodeIdFromRouteTb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{
Te6edf3viewModelScopeTb4b4b4.Te6edf3launch Tb4b4b4{
Te6edf3nodeRequestActionsTb4b4b4.Te6edf3requestNeighborInfoTb4b4b4(Tffa657itTb4b4b4, Te6edf3stateTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3nodeTff7b72?.Te6edf3userTff7b72?.Te6edf3long_name Tff7b72?: Ta5d6ff"Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72fun Td2a8ffshowLogDetailTb4b4b4(Te6edf3titleResTb4b4b4: Te6edf3StringResourceTb4b4b4, Te6edf3annotatedMessageTb4b4b4: Te6edf3AnnotatedStringTb4b4b4) Tb4b4b4{
Te6edf3alertManagerTb4b4b4.Te6edf3showAlertTb4b4b4(
Te6edf3titleRes Tff7b72= Te6edf3titleResTb4b4b4,
Te6edf3composableMessage Tff7b72= Tb4b4b4{ Te6edf3SelectionContainer Tb4b4b4{ Te6edf3TextTb4b4b4(Te6edf3text Tff7b72= Te6edf3annotatedMessageTb4b4b4) Tb4b4b4} Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tff7b72fun Td2a8ffshowTracerouteDetailTb4b4b4(
Te6edf3annotatedMessageTb4b4b4: Te6edf3AnnotatedStringTb4b4b4,
Te6edf3requestIdTb4b4b4: Tffa657IntTb4b4b4,
Te6edf3responseLogUuidTb4b4b4: Tffa657StringTb4b4b4,
Te6edf3overlayTb4b4b4: Te6edf3TracerouteOverlay?Tb4b4b4,
Te6edf3onViewOnMapTb4b4b4: Tb4b4b4(Tffa657IntTb4b4b4, Tffa657StringTb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4,
Tb4b4b4) Tb4b4b4{
Te6edf3safeLaunchTb4b4b4(Te6edf3tag Tff7b72= Ta5d6ff"Ta5d6ffshowTracerouteDetailTa5d6ff"Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3snapshotPositions Tff7b72= Te6edf3tracerouteSnapshotRepositoryTb4b4b4.Te6edf3getSnapshotPositionsTb4b4b4(Te6edf3responseLogUuidTb4b4b4)Tb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)
Te6edf3alertManagerTb4b4b4.Te6edf3showAlertTb4b4b4(
Te6edf3titleRes Tff7b72= Te6edf3ResTb4b4b4.Te6edf3stringTb4b4b4.Te6edf3tracerouteTb4b4b4,
Te6edf3composableMessage Tff7b72= Tb4b4b4{ Te6edf3SelectionContainer Tb4b4b4{ Te6edf3TextTb4b4b4(Te6edf3text Tff7b72= Te6edf3annotatedMessageTb4b4b4) Tb4b4b4} Tb4b4b4}Tb4b4b4,
Te6edf3confirmTextRes Tff7b72= Te6edf3ResTb4b4b4.Te6edf3stringTb4b4b4.Te6edf3view_on_mapTb4b4b4,
Te6edf3onConfirm Tff7b72= Tb4b4b4{
Tff7b72val Te6edf3positionedNodeNums Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3snapshotPositionsTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3snapshotPositionsTb4b4b4.Te6edf3keys
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3positionedNodeNumsTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72val Te6edf3availability Tff7b72=
Te6edf3evaluateTracerouteMapAvailabilityTb4b4b4(
Te6edf3forwardRoute Tff7b72= Te6edf3overlayTff7b72?.Te6edf3forwardRouteTb4b4b4.Te6edf3orEmptyTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3returnRoute Tff7b72= Te6edf3overlayTff7b72?.Te6edf3returnRouteTb4b4b4.Te6edf3orEmptyTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3positionedNodeNums Tff7b72= Te6edf3positionedNodeNumsTb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3errorRes Tff7b72= Te6edf3availabilityTb4b4b4.Te6edf3toMessageResTb4b4b4(Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3errorRes Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
T8b949e// Post the error alert after the current alert is dismissed to avoid
T8b949e// the wrapping dismissAlert() in AlertManager immediately clearing it.
Te6edf3safeLaunchTb4b4b4(Te6edf3tag Tff7b72= Ta5d6ff"Ta5d6fftracerouteErrorTa5d6ff"Tb4b4b4) Tb4b4b4{
Te6edf3alertManagerTb4b4b4.Te6edf3showAlertTb4b4b4(Te6edf3titleRes Tff7b72= Te6edf3ResTb4b4b4.Te6edf3stringTb4b4b4.Te6edf3tracerouteTb4b4b4, Te6edf3messageRes Tff7b72= Te6edf3errorResTb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3onViewOnMapTb4b4b4(Te6edf3requestIdTb4b4b4, Te6edf3responseLogUuidTb4b4b4)
Tb4b4b4}
Tb4b4b4}Tb4b4b4,
Te6edf3dismissTextRes Tff7b72= Te6edf3ResTb4b4b4.Te6edf3stringTb4b4b4.Te6edf3okayTb4b4b4,
Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tff7b72fun Td2a8ffsetNodeIdTb4b4b4(Te6edf3idTb4b4b4: Tffa657IntTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3manualNodeIdTb4b4b4.Te6edf3value Tff7b72!Tff7b72= Te6edf3idTb4b4b4) Tb4b4b4{
Te6edf3manualNodeIdTb4b4b4.Te6edf3value Tff7b72= Te6edf3id
Tb4b4b4}
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffonClearedTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72superTb4b4b4.Te6edf3onClearedTb4b4b4(Tb4b4b4)
Te6edf3LoggerTb4b4b4.Te6edf3d Tb4b4b4{ Ta5d6ff"Ta5d6ffMetricsViewModel clearedTa5d6ff" Tb4b4b4}
Tb4b4b4}
T8b949e// region --- CSV Export ---
T8b949e/**
* Shared CSV export helper. Writes [header] then iterates [rows], converting each item to a CSV line via
* [rowMapper]. The mapper returns only the data columns; date and time columns are prepended automatically from the
* epoch-seconds timestamp extracted by [epochSeconds].
*/
Tff7b72private Tff7b72fun Tff7b72<Te6edf3TTff7b72> Td2a8ffexportCsvTb4b4b4(
Te6edf3uriTb4b4b4: Te6edf3CommonUriTb4b4b4,
Te6edf3headerTb4b4b4: Tffa657StringTb4b4b4,
Te6edf3rowsTb4b4b4: Te6edf3ListTff7b72<Te6edf3TTff7b72>Tb4b4b4,
Te6edf3epochSecondsTb4b4b4: Tb4b4b4(Te6edf3TTb4b4b4) Tff7b72-Tff7b72> Tffa657LongTb4b4b4,
Te6edf3rowMapperTb4b4b4: Tb4b4b4(Te6edf3TTb4b4b4) Tff7b72-Tff7b72> Tffa657StringTb4b4b4,
Tb4b4b4) Tb4b4b4{
Te6edf3safeLaunchTb4b4b4(Te6edf3context Tff7b72= Te6edf3dispatchersTb4b4b4.Te6edf3ioTb4b4b4, Te6edf3tag Tff7b72= Ta5d6ff"Ta5d6ffexportCsvTa5d6ff"Tb4b4b4) Tb4b4b4{
Te6edf3fileServiceTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3uriTb4b4b4) Tb4b4b4{ Te6edf3sink Tff7b72-Tff7b72>
Te6edf3sinkTb4b4b4.Te6edf3writeUtf8Tb4b4b4(Te6edf3headerTb4b4b4)
Te6edf3rowsTb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3item Tff7b72-Tff7b72>
Tff7b72val Te6edf3dt Tff7b72=
Te6edf3InstantTb4b4b4.Te6edf3fromEpochSecondsTb4b4b4(Te6edf3epochSecondsTb4b4b4(Te6edf3itemTb4b4b4)Tb4b4b4)Tb4b4b4.Te6edf3toLocalDateTimeTb4b4b4(Te6edf3TimeZoneTb4b4b4.Te6edf3currentSystemDefaultTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3sinkTb4b4b4.Te6edf3writeUtf8Tb4b4b4(Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3dtTb4b4b4.Te6edf3dateTffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700${Te6edf3dtTb4b4b4.Te6edf3timeTffd700}Ta5d6ff\"Ta5d6ff,Tffd700${Te6edf3rowMapperTb4b4b4(Te6edf3itemTb4b4b4)Tffd700}Ta5d6ff\Ta5d6ffnTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72fun Td2a8ffsavePositionCSVTb4b4b4(Te6edf3uriTb4b4b4: Te6edf3CommonUriTb4b4b4, Te6edf3dataTb4b4b4: Te6edf3ListTff7b72<Te6edf3orgTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3protoTb4b4b4.Te6edf3PositionTff7b72>Tb4b4b4) Tb4b4b4{
Te6edf3exportCsvTb4b4b4(
Te6edf3uri Tff7b72= Te6edf3uriTb4b4b4,
Te6edf3header Tff7b72= Ta5d6ff"Ta5d6ff\"Ta5d6ffdateTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6fftimeTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6fflatitudeTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6fflongitudeTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffaltitudeTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffsatsInViewTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffspeedTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffheadingTa5d6ff\"Ta5d6ff\Ta5d6ffnTa5d6ff"Tb4b4b4,
Te6edf3rows Tff7b72= Te6edf3dataTb4b4b4,
Te6edf3epochSeconds Tff7b72= Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3timeTb4b4b4.Te6edf3toLongTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4,
Tb4b4b4) Tb4b4b4{ Te6edf3pos Tff7b72-Tff7b72>
Tff7b72val Te6edf3lat Tff7b72= Tb4b4b4(Te6edf3posTb4b4b4.Te6edf3latitude_i Tff7b72?: T79c0ff0Tb4b4b4) Tff7b72* Te6edf3GeoConstantsTb4b4b4.Te6edf3DEG_D
Tff7b72val Te6edf3lon Tff7b72= Tb4b4b4(Te6edf3posTb4b4b4.Te6edf3longitude_i Tff7b72?: T79c0ff0Tb4b4b4) Tff7b72* Te6edf3GeoConstantsTb4b4b4.Te6edf3DEG_D
Tff7b72val Te6edf3heading Tff7b72= Te6edf3formatStringTb4b4b4(Ta5d6ff"Ta5d6ff%.2fTa5d6ff"Tb4b4b4, Tb4b4b4(Te6edf3posTb4b4b4.Te6edf3ground_track Tff7b72?: T79c0ff0Tb4b4b4) Tff7b72* Te6edf3GeoConstantsTb4b4b4.Te6edf3HEADING_DEGTb4b4b4)
Ta5d6ff"Ta5d6ff\"Tffd700$Te6edf3latTa5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700$Te6edf3lonTa5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700${Te6edf3posTb4b4b4.Te6edf3altitudeTffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700${Te6edf3posTb4b4b4.Te6edf3sats_in_viewTffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700${Te6edf3posTb4b4b4.Te6edf3ground_speedTffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700$Te6edf3headingTa5d6ff\"Ta5d6ff"
Tb4b4b4}
Tb4b4b4}
Tff7b72fun Td2a8ffsavePositionGpxTb4b4b4(Te6edf3uriTb4b4b4: Te6edf3CommonUriTb4b4b4, Te6edf3dataTb4b4b4: Te6edf3ListTff7b72<Te6edf3orgTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3protoTb4b4b4.Te6edf3PositionTff7b72>Tb4b4b4, Te6edf3trackNameTb4b4b4: Tffa657StringTb4b4b4) Tb4b4b4{
Te6edf3safeLaunchTb4b4b4(Te6edf3context Tff7b72= Te6edf3dispatchersTb4b4b4.Te6edf3ioTb4b4b4, Te6edf3tag Tff7b72= Ta5d6ff"Ta5d6ffexportGpxTa5d6ff"Tb4b4b4) Tb4b4b4{
Te6edf3fileServiceTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3uriTb4b4b4) Tb4b4b4{ Te6edf3sink Tff7b72-Tff7b72> Te6edf3sinkTb4b4b4.Te6edf3writeUtf8Tb4b4b4(Te6edf3buildGpxTb4b4b4(Te6edf3dataTb4b4b4, Te6edf3trackNameTb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tff7b72fun Td2a8ffsaveLocalStatsCSVTb4b4b4(Te6edf3uriTb4b4b4: Te6edf3CommonUriTb4b4b4, Te6edf3dataTb4b4b4: Te6edf3ListTff7b72<Te6edf3TelemetryTff7b72>Tb4b4b4) Tb4b4b4{
Te6edf3exportCsvTb4b4b4(
Te6edf3uri Tff7b72= Te6edf3uriTb4b4b4,
Te6edf3header Tff7b72=
Ta5d6ff"Ta5d6ff\"Ta5d6ffdateTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6fftimeTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffnoise_floor_dbmTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffuptime_secondsTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffchannel_utilizationTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffair_util_txTa5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Ta5d6ffpackets_txTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffpackets_rxTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffbad_rxTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffrx_dupeTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6fftx_relayTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6fftx_relay_canceledTa5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Ta5d6ffonline_nodesTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6fftotal_nodesTa5d6ff\"Ta5d6ff\Ta5d6ffnTa5d6ff"Tb4b4b4,
Te6edf3rows Tff7b72= Te6edf3dataTb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3local_stats Tff7b72!Tff7b72= Tff7b72null Tb4b4b4}Tb4b4b4,
Te6edf3epochSeconds Tff7b72= Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3timeTb4b4b4.Te6edf3toLongTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4,
Tb4b4b4) Tb4b4b4{ Te6edf3telemetry Tff7b72-Tff7b72>
Tff7b72val Te6edf3stats Tff7b72= Te6edf3telemetryTb4b4b4.Te6edf3local_stats
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3statsTff7b72?.Te6edf3noise_floor Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700${Te6edf3statsTff7b72?.Te6edf3uptime_seconds Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3statsTff7b72?.Te6edf3channel_utilization Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700${Te6edf3statsTff7b72?.Te6edf3air_util_tx Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3statsTff7b72?.Te6edf3num_packets_tx Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700${Te6edf3statsTff7b72?.Te6edf3num_packets_rx Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3statsTff7b72?.Te6edf3num_packets_rx_bad Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700${Te6edf3statsTff7b72?.Te6edf3num_rx_dupe Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3statsTff7b72?.Te6edf3num_tx_relay Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700${Te6edf3statsTff7b72?.Te6edf3num_tx_relay_canceled Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3statsTff7b72?.Te6edf3num_online_nodes Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700${Te6edf3statsTff7b72?.Te6edf3num_total_nodes Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff"
Tb4b4b4}
Tb4b4b4}
Tff7b72fun Td2a8ffsaveDeviceMetricsCSVTb4b4b4(Te6edf3uriTb4b4b4: Te6edf3CommonUriTb4b4b4, Te6edf3dataTb4b4b4: Te6edf3ListTff7b72<Te6edf3TelemetryTff7b72>Tb4b4b4) Tb4b4b4{
Te6edf3exportCsvTb4b4b4(
Te6edf3uri Tff7b72= Te6edf3uriTb4b4b4,
Te6edf3header Tff7b72=
Ta5d6ff"Ta5d6ff\"Ta5d6ffdateTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6fftimeTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffbatteryLevelTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffvoltageTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffchannelUtilizationTa5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Ta5d6ffairUtilTxTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffuptimeSecondsTa5d6ff\"Ta5d6ff\Ta5d6ffnTa5d6ff"Tb4b4b4,
Te6edf3rows Tff7b72= Te6edf3dataTb4b4b4,
Te6edf3epochSeconds Tff7b72= Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3timeTb4b4b4.Te6edf3toLongTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4,
Tb4b4b4) Tb4b4b4{ Te6edf3t Tff7b72-Tff7b72>
Tff7b72val Te6edf3dm Tff7b72= Te6edf3tTb4b4b4.Te6edf3device_metrics
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3dmTff7b72?.Te6edf3battery_level Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700${Te6edf3dmTff7b72?.Te6edf3voltage Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3dmTff7b72?.Te6edf3channel_utilization Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700${Te6edf3dmTff7b72?.Te6edf3air_util_tx Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3dmTff7b72?.Te6edf3uptime_seconds Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff"
Tb4b4b4}
Tb4b4b4}
Tff7b72fun Td2a8ffsaveEnvironmentMetricsCSVTb4b4b4(Te6edf3uriTb4b4b4: Te6edf3CommonUriTb4b4b4, Te6edf3dataTb4b4b4: Te6edf3ListTff7b72<Te6edf3TelemetryTff7b72>Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3oneWireHeaders Tff7b72= Tb4b4b4(T79c0ff1.Tb4b4b4.Te6edf3TELEMETRY_CHANNEL_COUNTTb4b4b4)Tb4b4b4.Te6edf3joinToStringTb4b4b4(Ta5d6ff"Ta5d6ff,Ta5d6ff"Tb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ff\"Ta5d6ffoneWireTempTffd700$Te6edf3itTa5d6ff\"Ta5d6ff" Tb4b4b4}
Tff7b72val Te6edf3adcHeaders Tff7b72= Tb4b4b4(T79c0ff1.Tb4b4b4.Te6edf3TELEMETRY_CHANNEL_COUNTTb4b4b4)Tb4b4b4.Te6edf3joinToStringTb4b4b4(Ta5d6ff"Ta5d6ff,Ta5d6ff"Tb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ff\"Ta5d6ffadcVoltageTffd700$Te6edf3itTa5d6ff\"Ta5d6ff" Tb4b4b4}
Te6edf3exportCsvTb4b4b4(
Te6edf3uri Tff7b72= Te6edf3uriTb4b4b4,
Te6edf3header Tff7b72=
Ta5d6ff"Ta5d6ff\"Ta5d6ffdateTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6fftimeTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6fftemperatureTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffrelativeHumidityTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffbarometricPressureTa5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Ta5d6ffgasResistanceTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffiaqTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffwindSpeedTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffwindDirectionTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffsoilTemperatureTa5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Ta5d6ffsoilMoistureTa5d6ff\"Ta5d6ff,Tffd700$Te6edf3oneWireHeadersTa5d6ff,Tffd700$Te6edf3adcHeadersTa5d6ff\Ta5d6ffnTa5d6ff"Tb4b4b4,
Te6edf3rows Tff7b72= Te6edf3dataTb4b4b4,
Te6edf3epochSeconds Tff7b72= Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3timeTb4b4b4.Te6edf3toLongTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4,
Tb4b4b4) Tb4b4b4{ Te6edf3t Tff7b72-Tff7b72>
Tff7b72val Te6edf3em Tff7b72= Te6edf3tTb4b4b4.Te6edf3environment_metrics
T8b949e// An absent channel exports as an empty field, keeping it distinguishable from a measured 0°C / 0 V.
Tff7b72val Te6edf3oneWireValues Tff7b72=
Tb4b4b4(T79c0ff0 Te6edf3until Te6edf3TELEMETRY_CHANNEL_COUNTTb4b4b4)Tb4b4b4.Te6edf3joinToStringTb4b4b4(Ta5d6ff"Ta5d6ff,Ta5d6ff"Tb4b4b4) Tb4b4b4{ Te6edf3i Tff7b72-Tff7b72>
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3emTff7b72?.Te6edf3oneWireTemperatureTb4b4b4(Te6edf3iTb4b4b4)Tff7b72?.Te6edf3takeIf Tb4b4b4{ Tff7b72!Tffa657itTb4b4b4.Te6edf3isNaNTb4b4b4(Tb4b4b4) Tb4b4b4} Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff"
Tb4b4b4}
Tff7b72val Te6edf3adcValues Tff7b72=
Tb4b4b4(T79c0ff0 Te6edf3until Te6edf3TELEMETRY_CHANNEL_COUNTTb4b4b4)Tb4b4b4.Te6edf3joinToStringTb4b4b4(Ta5d6ff"Ta5d6ff,Ta5d6ff"Tb4b4b4) Tb4b4b4{ Te6edf3i Tff7b72-Tff7b72>
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3emTff7b72?.Te6edf3adcVoltageTb4b4b4(Te6edf3iTb4b4b4)Tff7b72?.Te6edf3takeIf Tb4b4b4{ Tff7b72!Tffa657itTb4b4b4.Te6edf3isNaNTb4b4b4(Tb4b4b4) Tb4b4b4} Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff"
Tb4b4b4}
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3emTff7b72?.Te6edf3temperature Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700${Te6edf3emTff7b72?.Te6edf3relative_humidity Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3emTff7b72?.Te6edf3barometric_pressure Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700${Te6edf3emTff7b72?.Te6edf3gas_resistance Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3emTff7b72?.Te6edf3iaq Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700${Te6edf3emTff7b72?.Te6edf3wind_speed Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3emTff7b72?.Te6edf3wind_direction Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700${Te6edf3emTff7b72?.Te6edf3soil_temperature Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3emTff7b72?.Te6edf3soil_moisture Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Tffd700$Te6edf3oneWireValuesTa5d6ff,Tffd700$Te6edf3adcValuesTa5d6ff"
Tb4b4b4}
Tb4b4b4}
Tff7b72fun Td2a8ffsaveSignalMetricsCSVTb4b4b4(Te6edf3uriTb4b4b4: Te6edf3CommonUriTb4b4b4, Te6edf3dataTb4b4b4: Te6edf3ListTff7b72<Te6edf3MeshPacketTff7b72>Tb4b4b4) Tb4b4b4{
Te6edf3exportCsvTb4b4b4(
Te6edf3uri Tff7b72= Te6edf3uriTb4b4b4,
Te6edf3header Tff7b72= Ta5d6ff"Ta5d6ff\"Ta5d6ffdateTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6fftimeTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffrssiTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffsnrTa5d6ff\"Ta5d6ff\Ta5d6ffnTa5d6ff"Tb4b4b4,
Te6edf3rows Tff7b72= Te6edf3dataTb4b4b4,
Te6edf3epochSeconds Tff7b72= Tb4b4b4{ Tb4b4b4(Tffa657itTb4b4b4.Te6edf3rxTimeOrNullTb4b4b4(Tb4b4b4) Tff7b72?: T79c0ff0Tb4b4b4)Tb4b4b4.Te6edf3toLongTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4,
Tb4b4b4) Tb4b4b4{ Te6edf3p Tff7b72-Tff7b72>
T8b949e// An absent rssi or snr exports as an empty field, matching the other optional metrics above. An empty
T8b949e// field and "0" must stay distinguishable: 0 dB is a real reading.
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3pTb4b4b4.Te6edf3rx_rssi Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700${Te6edf3pTb4b4b4.Te6edf3snrOrNullTb4b4b4(Tb4b4b4) Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff"
Tb4b4b4}
Tb4b4b4}
Tff7b72fun Td2a8ffsavePowerMetricsCSVTb4b4b4(Te6edf3uriTb4b4b4: Te6edf3CommonUriTb4b4b4, Te6edf3dataTb4b4b4: Te6edf3ListTff7b72<Te6edf3TelemetryTff7b72>Tb4b4b4) Tb4b4b4{
Te6edf3exportCsvTb4b4b4(
Te6edf3uri Tff7b72= Te6edf3uriTb4b4b4,
Te6edf3header Tff7b72=
Ta5d6ff"Ta5d6ff\"Ta5d6ffdateTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6fftimeTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffch1VoltageTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffch1CurrentTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffch2VoltageTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffch2CurrentTa5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Ta5d6ffch3VoltageTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffch3CurrentTa5d6ff\"Ta5d6ff\Ta5d6ffnTa5d6ff"Tb4b4b4,
Te6edf3rows Tff7b72= Te6edf3dataTb4b4b4,
Te6edf3epochSeconds Tff7b72= Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3timeTb4b4b4.Te6edf3toLongTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4,
Tb4b4b4) Tb4b4b4{ Te6edf3t Tff7b72-Tff7b72>
Tff7b72val Te6edf3pm Tff7b72= Te6edf3tTb4b4b4.Te6edf3power_metrics
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3pmTff7b72?.Te6edf3ch1_voltage Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700${Te6edf3pmTff7b72?.Te6edf3ch1_current Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3pmTff7b72?.Te6edf3ch2_voltage Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700${Te6edf3pmTff7b72?.Te6edf3ch2_current Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3pmTff7b72?.Te6edf3ch3_voltage Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff\"Tffd700${Te6edf3pmTff7b72?.Te6edf3ch3_current Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff"
Tb4b4b4}
Tb4b4b4}
T8b949e/**
* Exports the air quality log. [data] carries the derived NowCast AQI alongside each reading (the telemetry proto
* has no AQI field), so the export matches what the graph and table show rather than dropping the derived column.
*/
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffCyclomaticComplexMethodTa5d6ff"Tb4b4b4)
Tff7b72internal Tff7b72fun Td2a8ffsaveAirQualityMetricsCSVTb4b4b4(Te6edf3uriTb4b4b4: Te6edf3CommonUriTb4b4b4, Te6edf3dataTb4b4b4: Te6edf3ListTff7b72<Te6edf3AirQualitySampleTff7b72>Tb4b4b4) Tb4b4b4{
Te6edf3exportCsvTb4b4b4(
Te6edf3uri Tff7b72= Te6edf3uriTb4b4b4,
Te6edf3header Tff7b72=
Ta5d6ff"Ta5d6ff\"Ta5d6ffdateTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6fftimeTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffaqiTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffpm10_standardTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffpm25_standardTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffpm100_standardTa5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Ta5d6ffpm10_environmentalTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffpm25_environmentalTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffpm100_environmentalTa5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Ta5d6ffparticles_03umTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffparticles_05umTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffparticles_10umTa5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Ta5d6ffparticles_25umTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffparticles_50umTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffparticles_100umTa5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Ta5d6ffco2Ta5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffco2_temperatureTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffco2_humidityTa5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Ta5d6ffform_formaldehydeTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffform_humidityTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffform_temperatureTa5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Ta5d6ffpm40_standardTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffparticles_40umTa5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Ta5d6ffpm_temperatureTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffpm_humidityTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffpm_voc_idxTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ffpm_nox_idxTa5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Ta5d6ffparticles_tpsTa5d6ff\"Ta5d6ff\Ta5d6ffnTa5d6ff"Tb4b4b4,
Te6edf3rows Tff7b72= Te6edf3dataTb4b4b4,
Te6edf3epochSeconds Tff7b72= Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3telemetryTb4b4b4.Te6edf3timeTb4b4b4.Te6edf3toLongTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4,
Tb4b4b4) Tb4b4b4{ Te6edf3sample Tff7b72-Tff7b72>
T8b949e// Present-and-zero is a real reading and must be exported (matching the chart/card); only a genuinely
T8b949e// absent field (null) renders as an empty cell. No zero-suppression guards here.
Tff7b72val Te6edf3aq Tff7b72= Te6edf3sampleTb4b4b4.Te6edf3telemetryTb4b4b4.Te6edf3air_quality_metrics
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3sampleTb4b4b4.Te6edf3aqi Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3aqTff7b72?.Te6edf3pm10_standard Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3aqTff7b72?.Te6edf3pm25_standard Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3aqTff7b72?.Te6edf3pm100_standard Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3aqTff7b72?.Te6edf3pm10_environmental Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3aqTff7b72?.Te6edf3pm25_environmental Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3aqTff7b72?.Te6edf3pm100_environmental Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3aqTff7b72?.Te6edf3particles_03um Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3aqTff7b72?.Te6edf3particles_05um Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3aqTff7b72?.Te6edf3particles_10um Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3aqTff7b72?.Te6edf3particles_25um Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3aqTff7b72?.Te6edf3particles_50um Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3aqTff7b72?.Te6edf3particles_100um Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3aqTff7b72?.Te6edf3co2 Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3aqTff7b72?.Te6edf3co2_temperature Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3aqTff7b72?.Te6edf3co2_humidity Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3aqTff7b72?.Te6edf3form_formaldehyde Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3aqTff7b72?.Te6edf3form_humidity Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3aqTff7b72?.Te6edf3form_temperature Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3aqTff7b72?.Te6edf3pm40_standard Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3aqTff7b72?.Te6edf3particles_40um Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3aqTff7b72?.Te6edf3pm_temperature Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3aqTff7b72?.Te6edf3pm_humidity Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3aqTff7b72?.Te6edf3pm_voc_idx Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3aqTff7b72?.Te6edf3pm_nox_idx Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff\"Tffd700${Te6edf3aqTff7b72?.Te6edf3particles_tps Tff7b72?: Ta5d6ff"Ta5d6ff"Tffd700}Ta5d6ff\"Ta5d6ff"
Tb4b4b4}
Tb4b4b4}
T8b949e// endregion
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffMagicNumberTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffCyclomaticComplexMethodTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffReturnCountTa5d6ff"Tb4b4b4)
Tff7b72fun Td2a8ffdecodePaxFromLogTb4b4b4(Te6edf3logTb4b4b4: Te6edf3MeshLogTb4b4b4)Tb4b4b4: Te6edf3ProtoPaxcount? Tb4b4b4{
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3packet Tff7b72= Te6edf3logTb4b4b4.Te6edf3fromRadioTb4b4b4.Te6edf3packet
Tff7b72val Te6edf3decoded Tff7b72= Te6edf3packetTff7b72?.Te6edf3decoded
Tff7b72if Tb4b4b4(Te6edf3packet Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3decoded Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3decodedTb4b4b4.Te6edf3portnum Tff7b72=Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3PAXCOUNTER_APPTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3decodedTb4b4b4.Te6edf3want_response Tff7b72=Tff7b72= Tff7b72trueTb4b4b4) Tff7b72return Tff7b72null
Tff7b72val Te6edf3pax Tff7b72= Te6edf3ProtoPaxcountTb4b4b4.Te6edf3ADAPTERTb4b4b4.Te6edf3decodeTb4b4b4(Te6edf3decodedTb4b4b4.Te6edf3payloadTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3paxTb4b4b4.Te6edf3ble Tff7b72!Tff7b72= T79c0ff0 Tff7b72|Tff7b72| Te6edf3paxTb4b4b4.Te6edf3wifi Tff7b72!Tff7b72= T79c0ff0 Tff7b72|Tff7b72| Te6edf3paxTb4b4b4.Te6edf3uptime Tff7b72!Tff7b72= T79c0ff0Tb4b4b4) Tff7b72return Te6edf3pax
Tb4b4b4}
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3eTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffFailed to parse Paxcount from binary dataTa5d6ff" Tb4b4b4}
Tb4b4b4}
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3base64 Tff7b72= Te6edf3logTb4b4b4.Te6edf3raw_messageTb4b4b4.Te6edf3trimTb4b4b4(Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3base64Tb4b4b4.Te6edf3matchesTb4b4b4(Te6edf3RegexTb4b4b4(Ta5d6ff"Ta5d6ff^[A-Za-z0-9+/=Ta5d6ff\\Ta5d6ffrTa5d6ff\\Ta5d6ffn]+$Ta5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3bytes Tff7b72= Te6edf3decodeBase64Tb4b4b4(Te6edf3base64Tb4b4b4)
Tff7b72return Te6edf3ProtoPaxcountTb4b4b4.Te6edf3ADAPTERTb4b4b4.Te6edf3decodeTb4b4b4(Te6edf3bytesTb4b4b4)
Tb4b4b4} Tff7b72else Tff7b72if Tb4b4b4(Te6edf3base64Tb4b4b4.Te6edf3matchesTb4b4b4(Te6edf3RegexTb4b4b4(Ta5d6ff"Ta5d6ff^[0-9a-fA-F]+$Ta5d6ff"Tb4b4b4)Tb4b4b4) Tff7b72&Tff7b72& Te6edf3base64Tb4b4b4.Te6edf3length Tff7b72% T79c0ff2 Tff7b72=Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3bytes Tff7b72= Te6edf3base64Tb4b4b4.Te6edf3chunkedTb4b4b4(T79c0ff2Tb4b4b4)Tb4b4b4.Te6edf3map Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3toIntTb4b4b4(T79c0ff1T79c0ff6Tb4b4b4)Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}Tb4b4b4.Te6edf3toByteArrayTb4b4b4(Tb4b4b4)
Tff7b72return Te6edf3ProtoPaxcountTb4b4b4.Te6edf3ADAPTERTb4b4b4.Te6edf3decodeTb4b4b4(Te6edf3bytesTb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
Te6edf3LoggerTb4b4b4.Te6edf3eTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffFailed to parse Paxcount from decoded dataTa5d6ff" Tb4b4b4}
Tb4b4b4}
Tff7b72return Tff7b72null
Tb4b4b4}
Tff7b72protected Tff7b72fun Td2a8ffdecodeBase64Tb4b4b4(Te6edf3base64Tb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3ByteArray Tff7b72= Te6edf3base64Tb4b4b4.Te6edf3decodeBase64Tb4b4b4(Tb4b4b4)Tff7b72?.Te6edf3toByteArrayTb4b4b4(Tb4b4b4) Tff7b72?: Te6edf3ByteArrayTb4b4b4(T79c0ff0Tb4b4b4)
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffbuildGpxTb4b4b4(Te6edf3positionsTb4b4b4: Te6edf3ListTff7b72<Te6edf3orgTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3protoTb4b4b4.Te6edf3PositionTff7b72>Tb4b4b4, Te6edf3trackNameTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Tffa657String Tb4b4b4{
Tff7b72val Te6edf3trkpts Tff7b72= Te6edf3buildString Tb4b4b4{
Tff7b72for Tb4b4b4(Te6edf3pos Tff7b72in Te6edf3positionsTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3lat Tff7b72= Te6edf3formatStringTb4b4b4(Ta5d6ff"Ta5d6ff%.7fTa5d6ff"Tb4b4b4, Tb4b4b4(Te6edf3posTb4b4b4.Te6edf3latitude_i Tff7b72?: T79c0ff0Tb4b4b4) Tff7b72* Te6edf3GeoConstantsTb4b4b4.Te6edf3DEG_DTb4b4b4)
Tff7b72val Te6edf3lon Tff7b72= Te6edf3formatStringTb4b4b4(Ta5d6ff"Ta5d6ff%.7fTa5d6ff"Tb4b4b4, Tb4b4b4(Te6edf3posTb4b4b4.Te6edf3longitude_i Tff7b72?: T79c0ff0Tb4b4b4) Tff7b72* Te6edf3GeoConstantsTb4b4b4.Te6edf3DEG_DTb4b4b4)
Te6edf3appendTb4b4b4(Ta5d6ff"Ta5d6ff <trkpt lat=Ta5d6ff\"Tffd700$Te6edf3latTa5d6ff\"Ta5d6ff lon=Ta5d6ff\"Tffd700$Te6edf3lonTa5d6ff\"Ta5d6ff>Ta5d6ff"Tb4b4b4)
Tff7b72if Tb4b4b4(Tb4b4b4(Te6edf3posTb4b4b4.Te6edf3altitude Tff7b72?: T79c0ff0Tb4b4b4) Tff7b72!Tff7b72= T79c0ff0Tb4b4b4) Te6edf3appendTb4b4b4(Ta5d6ff"Ta5d6ff<ele>Tffd700${Te6edf3posTb4b4b4.Te6edf3altitudeTffd700}Ta5d6ff</ele>Ta5d6ff"Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3posTb4b4b4.Te6edf3time Tff7b72> T79c0ff0Tb4b4b4) Te6edf3appendTb4b4b4(Ta5d6ff"Ta5d6ff<time>Tffd700${Te6edf3InstantTb4b4b4.Te6edf3fromEpochSecondsTb4b4b4(Te6edf3posTb4b4b4.Te6edf3timeTb4b4b4.Te6edf3toLongTb4b4b4(Tb4b4b4)Tb4b4b4)Tffd700}Ta5d6ff</time>Ta5d6ff"Tb4b4b4)
Te6edf3appendTb4b4b4(Ta5d6ff"Ta5d6ff</trkpt>Ta5d6ff\Ta5d6ffnTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tff7b72val Te6edf3name Tff7b72= Te6edf3trackNameTb4b4b4.Te6edf3replaceTb4b4b4(Ta5d6ff"Ta5d6ff&Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff&Ta5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3replaceTb4b4b4(Ta5d6ff"Ta5d6ff<Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff<Ta5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3replaceTb4b4b4(Ta5d6ff"Ta5d6ff>Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff>Ta5d6ff"Tb4b4b4)
Tff7b72return Ta5d6ff"""Ta5d6ff<?xml version=Ta5d6ff"Ta5d6ff1.0Ta5d6ff"Ta5d6ff encoding=Ta5d6ff"Ta5d6ffUTF-8Ta5d6ff"Ta5d6ff?>
gpx version=Ta5d6ff"Ta5d6ff1.1Ta5d6ff"Ta5d6ff creator=Ta5d6ff"Ta5d6ffMeshtasticTa5d6ff"Ta5d6ff xmlns=Ta5d6ff"Ta5d6ffhttp://www.topografix.com/GPX/1/1Ta5d6ff"Ta5d6ff>
<trk>
<name>Tffd700$Te6edf3nameTa5d6ff</name>
<trkseg>
Tffd700$Te6edf3trkptsTa5d6ff </trkseg>
</trk>
/gpx>Ta5d6ff"""
Tb4b4b4}
Served by rngit 1.4.2 - Generated in 0.17s